home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / Sample Code / Games / GlyphaIV / G4Enemy.c next >
Encoding:
Text File  |  2000-09-28  |  44.9 KB  |  1,408 lines  |  [TEXT/MPS ]

  1.  
  2. //============================================================================
  3. //----------------------------------------------------------------------------
  4. //                                    Enemy.c
  5. //----------------------------------------------------------------------------
  6. //============================================================================
  7.  
  8. // This file contains all enemy related functions (enemy "AI").  It handles…
  9. // the enemy decision making proccess, moves the enemies, etc.
  10.  
  11. #include "G4Externs.h"
  12.  
  13.  
  14. #define kEnemyImpulse            8
  15.  
  16. #define kOwlMaxHVel                96
  17. #define kOwlMaxVVel                320
  18. #define kOwlHeightSmell            96
  19. #define kOwlFlapImpulse            32
  20.  
  21. #define kWolfMaxHVel            128
  22. #define kWolfMaxVVel            400
  23. #define kWolfHeightSmell        160
  24. #define kWolfFlapImpulse        48
  25.  
  26. #define kJackalMaxHVel            192
  27. #define kJackalMaxVVel            512
  28. #define kJackalHeightSmell        240
  29. #define kJackalFlapImpulse        72
  30.  
  31.  
  32. Boolean SetEnemyInitialLocation (Rect *);
  33. void SetEnemyAttributes (short);
  34. short AssignNewAltitude (void);
  35. void InitEnemy (short, Boolean);
  36. void CheckEnemyPlatformHit (short);
  37. void CheckEnemyRoofCollision (short);
  38. void HandleIdleEnemies (short);
  39. void HandleFlyingEnemies (short);
  40. void HandleWalkingEnemy (short);
  41. void HandleSpawningEnemy (short);
  42. void HandleFallingEnemy (short);
  43. void HandleEggEnemy (short);
  44. void ResolveEnemyPlayerHit (short);
  45.  
  46.  
  47. handInfo    theHand;
  48. eyeInfo        theEye;
  49. Rect        grabZone;
  50. short        deadEnemies, spawnedEnemies, numEnemiesThisLevel, numOwls;
  51.  
  52. extern    playerType    thePlayer;
  53. extern    enemyType    theEnemies[kMaxEnemies];
  54. extern    Rect        platformRects[6], enemyInitRects[5];
  55. extern    long        theScore;
  56. extern    short        numLedges, numEnemies, countDownTimer;
  57. extern    short        levelOn;
  58. extern    Boolean        evenFrame, doEnemyFlapSound, doEnemyScrapeSound;
  59.  
  60.  
  61. //==============================================================  Functions
  62. //--------------------------------------------------------------  SetEnemyInitialLocation
  63.  
  64. // When a new enemy is about to be "born", this function is called to determine…
  65. // the enemies starting location.  The only thing important here is that the enemy…
  66. // appears on a valid platform for the particular level we're on.  As well, which…
  67. // platform he (it) appears on should be random.
  68.  
  69. Boolean SetEnemyInitialLocation (Rect *theRect)
  70. {
  71.     short        where, possibilities;
  72.     Boolean        facing;
  73.     
  74.     possibilities = numLedges - 1;        // Determine number of valid platforms.
  75.     where = RandomInt(possibilities);    // Choose one at random.
  76.     *theRect = enemyInitRects[where];    // Initially place enemy at default location.
  77.     
  78.     switch (where)                        // Determine if enemy facing left or right.
  79.     {                                    // It depends upon which platform they're on.
  80.         case 0:                            // These are the left-most platforms.
  81.         case 2:
  82.         facing = TRUE;                    // Enemy will face right.
  83.         break;
  84.         
  85.         case 3:                            // Special case for the center platform.
  86.         if (RandomInt(2) == 0)            // Enemy randomly faces either left or right.
  87.             facing = TRUE;
  88.         else
  89.             facing = FALSE;
  90.         break;
  91.         
  92.         default:                        // Catch remaining (right-most) platforms.
  93.         facing = FALSE;                    // Enemy will face left.
  94.         break;
  95.     }
  96.     
  97.     if ((levelOn % 5) == 4)                // Handle special case for Egg Wave
  98.     {                                    // Re-define enemy bounds.
  99.         theRect->left += 12 + RandomInt(48) - 24;
  100.         theRect->right = theRect->left + 24;
  101.         theRect->top = theRect->bottom - 24;
  102.     }
  103.     
  104.     return (facing);
  105. }
  106.  
  107. //--------------------------------------------------------------  SetEnemyAttributes
  108.  
  109. // Depending upon the type of enemy this function is passed (there are three…
  110. // types of sphinx enemies), this function sets up that enemies various…
  111. // attributes - such as maximum vertical velocity, etc.
  112.  
  113. void SetEnemyAttributes (short i)
  114. {
  115.     short        h;
  116.                                             // Point enemy toward center of screen.
  117.     h = (theEnemies[i].dest.left + theEnemies[i].dest.right) >> 1;
  118.     if (h < 320)                            // If enemy in left half of screen…
  119.         theEnemies[i].facingRight = TRUE;    // the enemy will face to the right.
  120.     else                                    // Otherwise, if in right half of screen…
  121.         theEnemies[i].facingRight = FALSE;    // face to the left.
  122.     
  123.     switch (theEnemies[i].kind)                // Okay, depending upon what "kind" of enemy…
  124.     {                                        // we're dealing with....
  125.         case kOwl:                            // The owl is the simplest (wimpiest) enemy.
  126.         if (theEnemies[i].facingRight)        // Choose which graphic to use.
  127.             theEnemies[i].srcNum = 0;
  128.         else
  129.             theEnemies[i].srcNum = 2;
  130.                                             // Set owl's velocity limitations.
  131.         theEnemies[i].maxHVel = kOwlMaxHVel;
  132.         theEnemies[i].maxVVel = kOwlMaxVVel;
  133.                                             // This is the distance within which he will…
  134.                                             // pursue the player (it's strictly Y distance).
  135.         theEnemies[i].heightSmell = kOwlHeightSmell;
  136.                                             // This is how powerful the owl's "flap" is.
  137.         theEnemies[i].flapImpulse = kOwlFlapImpulse;
  138.         break;
  139.         
  140.         case kWolf:                            // The wolf sphinx is of medium difficulty.
  141.         if (theEnemies[i].facingRight)        // Choose which graphic to use.
  142.             theEnemies[i].srcNum = 4;
  143.         else
  144.             theEnemies[i].srcNum = 6;
  145.                                             // Set wolf's velocity limitations.
  146.         theEnemies[i].maxHVel = kWolfMaxHVel;
  147.         theEnemies[i].maxVVel = kWolfMaxVVel;
  148.                                             // This is the distance within which he will…
  149.                                             // pursue the player (it's strictly Y distance).
  150.         theEnemies[i].heightSmell = kWolfHeightSmell;
  151.                                             // This is how powerful the wolf's "flap" is.
  152.         theEnemies[i].flapImpulse = kWolfFlapImpulse;
  153.         break;
  154.         
  155.         case kJackal:                        // The jackal is the swiftest, toughest enemy.
  156.         if (theEnemies[i].facingRight)        // Choose which graphic to use.
  157.             theEnemies[i].srcNum = 8;
  158.         else
  159.             theEnemies[i].srcNum = 10;
  160.                                             // Set jackal's velocity limitations.
  161.         theEnemies[i].maxHVel = kJackalMaxHVel;
  162.         theEnemies[i].maxVVel = kJackalMaxVVel;
  163.                                             // This is the distance within which he will…
  164.                                             // pursue the player (it's strictly Y distance).
  165.         theEnemies[i].heightSmell = kJackalHeightSmell;
  166.                                             // This is how powerful the jackal's "flap" is.
  167.         theEnemies[i].flapImpulse = kJackalFlapImpulse;
  168.         break;
  169.     }
  170. }
  171.  
  172. //--------------------------------------------------------------  AssignNewAltitude
  173.  
  174. // The sphinxes "patrol" specific altitudes in the arena.  After wrapping around…
  175. // the screen a few times, they randomly select a new altitude to patrol (this…
  176. // keeps the player from finding a "safe" place to stand.  This function chooses…
  177. // a new altitude for the enemy to patrol.
  178.  
  179. short AssignNewAltitude (void)
  180. {
  181.     short        which, altitude;
  182.     
  183.     which = RandomInt(4);        // There are only 4 "patrol altitudes".
  184.     switch (which)                // Depending on which random number came up…
  185.     {
  186.         case 0:                    // This is just below the ceiling.
  187.         altitude = 65 << 4;
  188.         break;
  189.         
  190.         case 1:                    // This is below the top platforms but above the…
  191.         altitude = 150 << 4;    // center platform.
  192.         break;
  193.         
  194.         case 2:                    // This is just below the center platform.
  195.         altitude = 245 << 4;
  196.         break;
  197.         
  198.         case 3:                    // This is striahgt across the lava pit.
  199.         altitude = 384 << 4;
  200.         break;
  201.     }
  202.     
  203.     return (altitude);
  204. }
  205.  
  206. //--------------------------------------------------------------  InitEnemy
  207.  
  208. // This resets an enemies info.  It is called when a new enemy is to be born.
  209. // It is called if an egg is about to hatch, if a new level has begun, or if…
  210. // if it is simply time to add a new enemy.
  211.  
  212. void InitEnemy (short i, Boolean reincarnated)
  213. {
  214.     Boolean        facing;
  215.     
  216.     if (spawnedEnemies < numEnemiesThisLevel)    // New enemy to appear (in other words…
  217.     {                                            // this enemy is not hatched).
  218.                                                 // Call function to set new location.
  219.         facing = SetEnemyInitialLocation(&theEnemies[i].dest);
  220.         theEnemies[i].wasDest = theEnemies[i].dest;
  221.         theEnemies[i].h = theEnemies[i].dest.left << 4;
  222.         theEnemies[i].v = theEnemies[i].dest.top << 4;
  223.         theEnemies[i].wasH = theEnemies[i].h;    // Reset "old locations" variables.
  224.         theEnemies[i].wasV = theEnemies[i].v;
  225.                                                 // Assign the "patrol altitude".
  226.         theEnemies[i].targetAlt = theEnemies[i].v - (40 << 4);
  227.         theEnemies[i].hVel = 0;                    // Zero velocity vraiables.
  228.         theEnemies[i].vVel = 0;
  229.         theEnemies[i].pass = 0;                    // Zero number of times wrapped around.
  230.         if ((levelOn % 5) == 4)                    // If this is an Egg Wave…
  231.             theEnemies[i].mode = kEggTimer;        // set enemy in "wait to hatch" mode.
  232.         else                                    // Otherwise, just sut enemy in…
  233.             theEnemies[i].mode = kIdle;            // idle mode.
  234.         if (i < numOwls)                        // Determine what kind of enemy.
  235.             theEnemies[i].kind = kOwl;
  236.         else if (i > (numOwls + 6))    
  237.             theEnemies[i].kind = kJackal;
  238.         else
  239.             theEnemies[i].kind = kWolf;
  240.         theEnemies[i].facingRight = facing;
  241.         SetEnemyAttributes(i);                    // Initialize enemy attributes.
  242.         
  243.         if (reincarnated)                        // If this is an egg that will hatch…
  244.             theEnemies[i].frame = RandomInt(48) + 8 + (numOwls * 32);
  245.         else
  246.             theEnemies[i].frame = RandomInt(48) + 32 + (64 * i) + (numOwls * 32);
  247.         
  248.         if ((levelOn % 5) == 4)                    // If this is an Egg Wave
  249.             theEnemies[i].kind--;                // Decrement "kind" (since it's incremented…
  250.                                                 // when they hatch).
  251.         spawnedEnemies++;                        // Keep track of number of enemies active.
  252.     }
  253. }
  254.  
  255. //--------------------------------------------------------------  GenerateEnemies
  256.  
  257. // This function is called only for a new level.  It goes through and…
  258. // intializes a whole host of enemies in one go.
  259.  
  260. void GenerateEnemies (void)
  261. {
  262.     short        i;
  263.     
  264.     if ((levelOn % 5) == 4)            // If this is an Egg Wave…
  265.     {
  266.         numEnemies = kMaxEnemies;    // we insist upon the maximum number of enemies.
  267.         numEnemiesThisLevel = numEnemies;
  268.     }
  269.     else                            // If not an egg wave, use a formula to determine…
  270.     {                                // the max number of enemies that are to be active.
  271.         numEnemies = ((levelOn / 5) + 2) * 2;
  272.         if (numEnemies > kMaxEnemies)
  273.             numEnemies = kMaxEnemies;
  274.         numEnemiesThisLevel = numEnemies * 2;
  275.     }
  276.     
  277.     deadEnemies = 0;                // No dead enemies yet.
  278.     
  279.                                     // Use formula to determine the number of owls…
  280.                                     // to appear.  This number goes down as the levels…
  281.                                     // increase.  It is used not merely to determine…
  282.                                     // how many owls are to appear, but also how many…
  283.                                     // of the more advanced enemies.  For example, when…
  284.                                     // numOwls goes down to zero, all the enemies will…
  285.                                     // be of the more advanced breed (wolves and jackals).
  286.     numOwls = 4 - ((levelOn + 2) / 5);
  287.     if (numOwls < 0)
  288.         numOwls = 0;
  289.     
  290.     spawnedEnemies = 0;                // No enemies have been "born" yet.
  291.                                     // Go through and set up all the enemies.
  292.     for (i = 0; i < numEnemies; i++)
  293.         InitEnemy(i, FALSE);
  294. }
  295.  
  296. //--------------------------------------------------------------  CheckEnemyPlatformHit
  297.  
  298. // This is the enemy counterpart to a similarly named function that tests for…
  299. // player collsions with the platforms.
  300.  
  301. void CheckEnemyPlatformHit (short h)
  302. {
  303.     Rect        hRect, vRect, whoCares;
  304.     short        i, offset;
  305.     
  306.     for (i = 0; i < numLedges; i++)                    // Test all platforms.
  307.     {                                                // Do a simple bounds test.
  308.         if (SectRect(&theEnemies[h].dest, &platformRects[i], &whoCares))
  309.         {                                            // If the enemy has hit the platform…
  310.             hRect.left = theEnemies[h].dest.left;    // Determine if enemy hit platform sides.
  311.             hRect.right = theEnemies[h].dest.right;
  312.             hRect.top = theEnemies[h].wasDest.top;
  313.             hRect.bottom = theEnemies[h].wasDest.bottom;
  314.                                                     // Test this new special rect to see if…
  315.                                                     // the enemy hit on of the platform sides.
  316.             if (SectRect(&hRect, &platformRects[i], &whoCares))
  317.             {                                        // If enemy hit from side, see which side.
  318.                                                     // We handle left and right seperatrely…
  319.                                                     // so that there's no ambiguity as to…
  320.                                                     // what the new velocity and location…
  321.                                                     // of the enemy is.  If we did not do it…
  322.                                                     // this way, there is the chance that an…
  323.                                                     // enemy get's "stuck" on the edge of…
  324.                                                     // a platform (due to round-off errors).
  325.                 if (theEnemies[h].h > theEnemies[h].wasH)
  326.                 {                                    // Enemy was moving right (hit left side).
  327.                     offset = theEnemies[h].dest.right - platformRects[i].left;
  328.                                                     // Slide enemy "off" platform.
  329.                     theEnemies[h].dest.left -= offset;
  330.                     theEnemies[h].dest.right -= offset;
  331.                     theEnemies[h].h = theEnemies[h].dest.left << 4;
  332.                     theEnemies[h].wasH = theEnemies[h].h;
  333.                                                     // Bounce enemy (negate velocity).
  334.                     if (theEnemies[h].hVel > 0)
  335.                         theEnemies[h].hVel = -(theEnemies[h].hVel >> 1);
  336.                     else
  337.                         theEnemies[h].hVel = theEnemies[h].hVel >> 1;
  338.                 }
  339.                 if (theEnemies[h].h < theEnemies[h].wasH)
  340.                 {                                    // Enemy was moving left (hit right side).
  341.                     offset = platformRects[i].right - theEnemies[h].dest.left;
  342.                                                     // Slide enemy "off" platform.
  343.                     theEnemies[h].dest.left += offset;
  344.                     theEnemies[h].dest.right += offset;
  345.                     theEnemies[h].h = theEnemies[h].dest.left << 4;
  346.                     theEnemies[h].wasH = theEnemies[h].h;
  347.                                                     // Bounce enemy (negate velocity).
  348.                     if (theEnemies[h].hVel < 0)
  349.                         theEnemies[h].hVel = -(theEnemies[h].hVel >> 1);
  350.                     else
  351.                         theEnemies[h].hVel = theEnemies[h].hVel >> 1;
  352.                 }
  353.                 doEnemyScrapeSound = TRUE;            // Play a collision sound.
  354.                                                     // Flip enemy to face opposite direction.
  355.                 theEnemies[h].facingRight = !theEnemies[h].facingRight;
  356.             }
  357.             else                                    // Enemy didn't hit from side.
  358.             {                                        // See if enemy hit top/bottom.
  359.                 vRect.left = theEnemies[h].wasDest.left;
  360.                 vRect.right = theEnemies[h].wasDest.right;
  361.                 vRect.top = theEnemies[h].dest.top;
  362.                 vRect.bottom = theEnemies[h].dest.bottom;
  363.                                                     // Special "test rect" for top/bottom hit.
  364.                 if (SectRect(&vRect, &platformRects[i], &whoCares))
  365.                 {                                    // If hit the top/bottom of platform…
  366.                     if (theEnemies[h].mode == kFalling)
  367.                     {                                // Was the enemy a falling egg?
  368.                                                     // Bounce egg (with some inelasticity).
  369.                         theEnemies[i].hVel -= (theEnemies[i].hVel >> 3);
  370.                                                     // When the eggs velocity is between…
  371.                                                     // +/- 8, consider the egg at rest.
  372.                         if ((theEnemies[i].hVel < 8) && (theEnemies[i].hVel > -8))
  373.                         {
  374.                             if (theEnemies[i].hVel > 0)
  375.                                 theEnemies[i].hVel--;
  376.                             else if (theEnemies[i].hVel < 0)
  377.                                 theEnemies[i].hVel++;
  378.                         }
  379.                     }
  380.                                                     // Specifically, did enemy hit the top?
  381.                     if (theEnemies[h].v > theEnemies[h].wasV)
  382.                     {                                // Enemy heading down (hit platform top).
  383.                         offset = theEnemies[h].dest.bottom - platformRects[i].top;
  384.                                                     // Move enemy up off platform.
  385.                         theEnemies[h].dest.top -= offset;
  386.                         theEnemies[h].dest.bottom -= offset;
  387.                         theEnemies[h].v = theEnemies[h].dest.top << 4;
  388.                         theEnemies[h].wasV = theEnemies[h].v;
  389.                         if (theEnemies[h].vVel > kDontFlapVel)
  390.                             doEnemyScrapeSound = TRUE;
  391.                                                     // "Bounce" enemy.
  392.                         if (theEnemies[h].vVel > 0)
  393.                             theEnemies[h].vVel = -(theEnemies[h].vVel >> 1);
  394.                         else
  395.                             theEnemies[h].vVel = theEnemies[h].vVel >> 1;
  396.                         if ((theEnemies[h].vVel < 8) && (theEnemies[h].vVel > -8) && 
  397.                                 (theEnemies[h].hVel == 0) && (theEnemies[h].mode == kFalling))
  398.                         {                        // Here we handle an egg come to rest.
  399.                             if (((theEnemies[h].dest.right - 8) > platformRects[i].right) && 
  400.                                     (theEnemies[h].hVel == 0))
  401.                             {                    // Special case where egg right on edge.
  402.                                 theEnemies[h].hVel = 32;
  403.                             }
  404.                             else if (((theEnemies[h].dest.left + 8) < platformRects[i].left) && 
  405.                                     (theEnemies[h].hVel == 0))
  406.                             {                    // Special case where egg right on edge.
  407.                                 theEnemies[h].hVel = -32;
  408.                             }
  409.                             else                // If egg not on the edge of platform…
  410.                             {                    // switch to "timer" mode.
  411.                                 theEnemies[h].mode = kEggTimer;
  412.                                 theEnemies[h].frame = (numOwls * 96) + 128;
  413.                                 theEnemies[h].vVel = 0;
  414.                             }
  415.                         }
  416.                     }
  417.                     if (theEnemies[h].v < theEnemies[h].wasV)
  418.                     {                            // Enemy was rising - hit bottom of platform.
  419.                         offset = theEnemies[h].dest.top - platformRects[i].bottom;
  420.                                                 // Slide enemy off platform.
  421.                         theEnemies[h].dest.top -= offset;
  422.                         theEnemies[h].dest.bottom -= offset;
  423.                         theEnemies[h].v = theEnemies[h].dest.top << 4;
  424.                         theEnemies[h].wasV = theEnemies[h].v;
  425.                                                 // Play collision sound.
  426.                         doEnemyScrapeSound = TRUE;
  427.                                                 // "Bounce" enemy downward from platform.
  428.                         if (theEnemies[h].vVel < 0)
  429.                             theEnemies[h].vVel = -(theEnemies[h].vVel >> 2);
  430.                         else
  431.                             theEnemies[h].vVel = theEnemies[h].vVel >> 2;
  432.                         if ((theEnemies[h].vVel < 8) && (theEnemies[h].vVel > -8) && 
  433.                                 (theEnemies[h].hVel == 0) && (theEnemies[h].mode == kFalling))
  434.                         {
  435.                             theEnemies[h].mode = kEggTimer;
  436.                             theEnemies[h].frame = (numOwls * 96) + 128;
  437.                             theEnemies[h].vVel = 0;
  438.                         }
  439.                     }
  440.                 }
  441.             }
  442.         }
  443.     }
  444. }
  445.  
  446. //--------------------------------------------------------------  CheckEnemyRoofCollision
  447.  
  448. // Like the player counterpart, this function checks to see if an enemy has hit…
  449. // the ceiling or the lava.  It handles the consequences of both cases.
  450.  
  451. void CheckEnemyRoofCollision (short i)
  452. {
  453.     short        offset;
  454.     
  455.     if (theEnemies[i].dest.top < (kRoofHeight - 2))
  456.     {                    // If enemy has hit the ceiling…
  457.         offset = kRoofHeight - theEnemies[i].dest.top;
  458.                         // Move enemy down to a "legal" altitude.
  459.         theEnemies[i].dest.top += offset;
  460.         theEnemies[i].dest.bottom += offset;
  461.         theEnemies[i].v = theEnemies[i].dest.top << 4;
  462.                         // Play a collision sound.
  463.         doEnemyScrapeSound = TRUE;
  464.                         // Bounce enemy downward.
  465.         theEnemies[i].vVel = -(theEnemies[i].vVel >> 2);
  466.     }
  467.     else if (theEnemies[i].dest.top > kLavaHeight)
  468.     {                    // If enemy has fallen into lava…
  469.                         // kill that enemy.
  470.         theEnemies[i].mode = kDeadAndGone;
  471.         deadEnemies++;
  472.                         // Play a splash sound.
  473.         {
  474.             short left = theEnemies[i].dest.left;
  475.             short right = theEnemies[i].dest.right;
  476.             short bottom = theEnemies[i].dest.bottom;
  477.             short delta = (right - left);
  478.             short temp = left * 32;
  479.             short splashItr;
  480.             
  481.             for(splashItr = 0; splashItr < 32; splashItr++)
  482.             {
  483.                 StartPixelShatter(temp / 32, bottom, (theEnemies[i].hVel * 0.75), -(theEnemies[i].vVel * 0.75), kShatterLavaSplash);
  484.                 temp += delta;
  485.             }
  486.         }
  487.  
  488.         PlayExternalSound(kSplashSound, kSplashPriority);
  489.                         // Call up another from the ranks.
  490.         InitEnemy(i, TRUE);
  491.     }
  492. }
  493.  
  494. //--------------------------------------------------------------  HandleIdleEnemies
  495.  
  496. // The following functions handle the various enemy modes.  Enemies are…
  497. // considered to be in a specific mode and each mode is handled differently.
  498. // Idle enemies are ones who are "invisible" - not yet born.  While idle, a…
  499. // timer is ticking down - when it reaches zero, the enemy appears.
  500.  
  501. void HandleIdleEnemies (short i)
  502. {
  503.     theEnemies[i].frame--;                    // Decrement timer.
  504.     if (theEnemies[i].frame <= 0)            // If timer is zero or less…
  505.     {
  506.         theEnemies[i].mode = kSpawning;        // enemy is "born".
  507.         theEnemies[i].wasH = theEnemies[i].h;
  508.         theEnemies[i].wasV = theEnemies[i].v;
  509.         theEnemies[i].hVel = 0;
  510.         theEnemies[i].vVel = 0;
  511.         theEnemies[i].frame = 0;
  512.         SetEnemyAttributes(i);                // Initialize enemy attributes.
  513.         PlayExternalSound(kSpawnSound, kSpawnPriority);
  514.     }
  515. }
  516.  
  517. //--------------------------------------------------------------  HandleFlyingEnemies
  518.  
  519. // Once an enemy takes off from a platform, they will always be in flying mode…
  520. // unless they should be killed.  This function handles the flying mode.
  521.  
  522. void HandleFlyingEnemies (short i)
  523. {
  524.     short        dist;
  525.     Boolean        shouldFlap;
  526.                                 // Take into account gravity pulling enemy down.
  527.     theEnemies[i].vVel += kGravity;
  528.                                 // Get absolute difference in enemy/player altitude.
  529.     dist = thePlayer.dest.top - theEnemies[i].dest.top;
  530.     if (dist < 0)
  531.         dist = -dist;
  532.                                 // See if the player is within the enemy's "seek" range.
  533.     if ((dist < theEnemies[i].heightSmell) && 
  534.             ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)))
  535.     {                            // Enemy will actively seek the player.
  536.         if (thePlayer.dest.left < theEnemies[i].dest.left)
  537.         {                        // Determine if quicker to go left or right to get player.
  538.             dist = theEnemies[i].dest.left - thePlayer.dest.left;
  539.             if (dist < 320)        // Closest route is to the left.
  540.                 theEnemies[i].facingRight = FALSE;
  541.             else                // Closest route is to the right.
  542.                 theEnemies[i].facingRight = TRUE;
  543.         }
  544.         else if (thePlayer.dest.left > theEnemies[i].dest.left)
  545.         {                        // Determine if quicker to go left or right to get player.
  546.             dist = thePlayer.dest.left - theEnemies[i].dest.left;
  547.             if (dist < 320)        // Closest route is to the right.
  548.                 theEnemies[i].facingRight = TRUE;
  549.             else                // Closest route is to the left.
  550.                 theEnemies[i].facingRight = FALSE;
  551.         }
  552.                                 // Seek an altitude 16 pixels above player.
  553.         if (((theEnemies[i].v + 16) > thePlayer.v) && (evenFrame))
  554.             shouldFlap = TRUE;
  555.         else
  556.             shouldFlap = FALSE;
  557.     }
  558.     else                        // Else, player not within enemy's "seek" altitude.
  559.     {                            // Flap if necessary to maintain "patrol altitude".
  560.         if ((theEnemies[i].v > theEnemies[i].targetAlt) && (evenFrame))
  561.             shouldFlap = TRUE;
  562.         else
  563.             shouldFlap = FALSE;
  564.     }
  565.     
  566.     if (shouldFlap)                // If the enemy has determined that it needs to flap…
  567.     {                            // Give the enemy lift & play the flap sound.
  568.         theEnemies[i].vVel -= theEnemies[i].flapImpulse;
  569.         doEnemyFlapSound = TRUE;
  570.     }
  571.                                 // Enemy never hovers - must move right or left.
  572.     if (theEnemies[i].facingRight)
  573.     {                            // If enemy facing right - move enemy to the right.
  574.         theEnemies[i].hVel += kEnemyImpulse;
  575.         if (theEnemies[i].hVel > theEnemies[i].maxHVel)
  576.             theEnemies[i].hVel = theEnemies[i].maxHVel;
  577.                                 // Determine correct graphic for enemy.
  578.         switch (theEnemies[i].kind)
  579.         {
  580.             case kOwl:
  581.             if (shouldFlap)
  582.                 theEnemies[i].srcNum = 12;
  583.             else
  584.                 theEnemies[i].srcNum = 13;
  585.             break;
  586.             
  587.             case kWolf:
  588.             if (shouldFlap)
  589.                 theEnemies[i].srcNum = 16;
  590.             else
  591.                 theEnemies[i].srcNum = 17;
  592.             break;
  593.             
  594.             case kJackal:
  595.             if (shouldFlap)
  596.                 theEnemies[i].srcNum = 20;
  597.             else
  598.                 theEnemies[i].srcNum = 21;
  599.             break;
  600.         }
  601.         
  602.     }
  603.     else                        // If enemy not facing right (left) move to the left.
  604.     {
  605.         theEnemies[i].hVel -= kEnemyImpulse;
  606.         if (theEnemies[i].hVel < -theEnemies[i].maxHVel)
  607.             theEnemies[i].hVel = -theEnemies[i].maxHVel;
  608.                                 // Determine correct graphic for enemy.
  609.         switch (theEnemies[i].kind)
  610.         {
  611.             case kOwl:
  612.             if (shouldFlap)
  613.                 theEnemies[i].srcNum = 14;
  614.             else
  615.                 theEnemies[i].srcNum = 15;
  616.             break;
  617.             
  618.             case kWolf:
  619.             if (shouldFlap)
  620.                 theEnemies[i].srcNum = 18;
  621.             else
  622.                 theEnemies[i].srcNum = 19;
  623.             break;
  624.             
  625.             case kJackal:
  626.             if (shouldFlap)
  627.                 theEnemies[i].srcNum = 22;
  628.             else
  629.                 theEnemies[i].srcNum = 23;
  630.             break;
  631.         }
  632.     }
  633.                                         // Move enemy horizontally based on hori velocity.
  634.     theEnemies[i].h += theEnemies[i].hVel;
  635.     theEnemies[i].dest.left = theEnemies[i].h >> 4;
  636.     theEnemies[i].dest.right = theEnemies[i].dest.left + 64;
  637.                                         // Move enemy vertically based on vertical velocity.
  638.     theEnemies[i].v += theEnemies[i].vVel;
  639.     theEnemies[i].dest.top = theEnemies[i].v >> 4;
  640.     theEnemies[i].dest.bottom = theEnemies[i].dest.top + 40;
  641.                                         // Check for wrap-around.
  642.     if (theEnemies[i].dest.left > 640)
  643.     {                                    // If off right edge, wrap around to left side.
  644.         OffsetRect(&theEnemies[i].dest, -640, 0);
  645.         theEnemies[i].h = theEnemies[i].dest.left << 4;
  646.         OffsetRect(&theEnemies[i].wasDest, -640, 0);
  647.         theEnemies[i].pass++;            // Increment number of "wrap-arounds" for this enemy.
  648.         if (theEnemies[i].pass > 2)        // After two screen passes (wrap arounds)…
  649.         {                                // enemy patrols a new altitude.
  650.             theEnemies[i].targetAlt = AssignNewAltitude();
  651.             theEnemies[i].pass = 0;
  652.         }
  653.     }
  654.     else if (theEnemies[i].dest.right < 0)
  655.     {                                    // If off left edge, wrap around to right side.
  656.         OffsetRect(&theEnemies[i].dest, 640, 0);
  657.         theEnemies[i].h = theEnemies[i].dest.left << 4;
  658.         OffsetRect(&theEnemies[i].wasDest, 640, 0);
  659.         theEnemies[i].pass++;
  660.         if (theEnemies[i].pass > 2)
  661.         {
  662.             theEnemies[i].targetAlt = AssignNewAltitude();
  663.             theEnemies[i].pass = 0;
  664.         }
  665.     }
  666.                                         // Throw a touch of friction into the mix.
  667.     theEnemies[i].vVel -= theEnemies[i].vVel >> 4;
  668.                                         // Keep enemies from moving excessively fast.
  669.     if (theEnemies[i].vVel > theEnemies[i].maxVVel)
  670.         theEnemies[i].vVel = theEnemies[i].maxVVel;
  671.     else if (theEnemies[i].vVel < -theEnemies[i].maxVVel)
  672.         theEnemies[i].vVel = -theEnemies[i].maxVVel;
  673.     
  674.     CheckEnemyRoofCollision(i);            // Check for lava/celing collisions.
  675.     CheckEnemyPlatformHit(i);            // Check for platform collisions.
  676. }
  677.  
  678. //--------------------------------------------------------------  HandleWalkingEnemy
  679.  
  680. // This is a brief mode for an enemy.  When an enemy has hatched from an egg, it…
  681. // walks only for 8 game frames at which point it takes off and flies for the rest…
  682. // of its life.
  683.  
  684. void HandleWalkingEnemy (short i)
  685. {
  686.     if (theEnemies[i].facingRight)        // If enemy facing right, walk to the right.
  687.     {
  688.         theEnemies[i].dest.left += 6;    // Move enemy to right.
  689.         theEnemies[i].dest.right += 6;
  690.         switch (theEnemies[i].kind)        // Determine correct graphic for walking enemy.
  691.         {
  692.             case kOwl:
  693.             theEnemies[i].srcNum = 1 - theEnemies[i].srcNum;
  694.             break;
  695.             
  696.             case kWolf:
  697.             theEnemies[i].srcNum = 9 - theEnemies[i].srcNum;
  698.             break;
  699.             
  700.             case kJackal:
  701.             theEnemies[i].srcNum = 17 - theEnemies[i].srcNum;
  702.             break;
  703.         }
  704.         theEnemies[i].hVel = 6 << 4;
  705.     }
  706.     else                                // If enemy not facing right (left), walk to the left.
  707.     {
  708.         theEnemies[i].dest.left -= 6;    // Move enemy to left.
  709.         theEnemies[i].dest.right -= 6;
  710.         switch (theEnemies[i].kind)        // Determine correct graphic for walking enemy.
  711.         {
  712.             case kOwl:
  713.             theEnemies[i].srcNum = 5 - theEnemies[i].srcNum;
  714.             break;
  715.             
  716.             case kWolf:
  717.             theEnemies[i].srcNum = 13 - theEnemies[i].srcNum;
  718.             break;
  719.             
  720.             case kJackal:
  721.             theEnemies[i].srcNum = 21 - theEnemies[i].srcNum;
  722.             break;
  723.         }
  724.         theEnemies[i].hVel = -6 << 4;
  725.     }
  726.     
  727.     theEnemies[i].frame++;                // Increment number of frames it has walked for.
  728.     if (theEnemies[i].frame >= 8)        // If over 8, enemy takes off an flies.
  729.     {
  730.         theEnemies[i].mode = kFlying;    // Switch to flying mode.
  731.         theEnemies[i].frame = 0;        // Reset "frame" variable.
  732.         switch (theEnemies[i].kind)        // Determine correct graphic for flying enemy.
  733.         {
  734.             case kOwl:
  735.             if (theEnemies[i].facingRight)
  736.                 theEnemies[i].srcNum = 12;
  737.             else
  738.                 theEnemies[i].srcNum = 14;
  739.             break;
  740.             
  741.             case kWolf:
  742.             if (theEnemies[i].facingRight)
  743.                 theEnemies[i].srcNum = 16;
  744.             else
  745.                 theEnemies[i].srcNum = 18;
  746.             break;
  747.             
  748.             case kJackal:
  749.             if (theEnemies[i].facingRight)
  750.                 theEnemies[i].srcNum = 20;
  751.             else
  752.                 theEnemies[i].srcNum = 22;
  753.             break;
  754.         }
  755.                                         // Re-size enemy bounds to a "flying" size.
  756.         theEnemies[i].dest.left -= 8;
  757.         theEnemies[i].dest.right += 8;
  758.         theEnemies[i].dest.bottom = theEnemies[i].dest.top + 40;
  759.         theEnemies[i].h = theEnemies[i].dest.left * 16;
  760.         theEnemies[i].v = theEnemies[i].dest.top * 16;
  761.     }
  762. }
  763.  
  764. //--------------------------------------------------------------  HandleSpawningEnemy
  765.  
  766. // This is an enemy "rising out of a platform".  Either an egg has just hatched…
  767. // or a brand new enemy has been introduced.  Irregardless, the sphinx is born.
  768. // When the enemy is at its full height, it will begin to walk.
  769.  
  770. void HandleSpawningEnemy (short i)
  771. {
  772.     theEnemies[i].frame++;                // Advance timer.
  773.     if (theEnemies[i].frame >= 48)        // If timer >= 48, enemy begins to walk.
  774.     {
  775.         theEnemies[i].mode = kWalking;
  776.         theEnemies[i].frame = 0;
  777.         
  778.         switch (theEnemies[i].kind)        // Determine appropriate graphic.
  779.         {
  780.             case kOwl:
  781.             if (theEnemies[i].facingRight)
  782.                 theEnemies[i].srcNum = 0;
  783.             else
  784.                 theEnemies[i].srcNum = 2;
  785.             break;
  786.             
  787.             case kWolf:
  788.             if (theEnemies[i].facingRight)
  789.                 theEnemies[i].srcNum = 4;
  790.             else
  791.                 theEnemies[i].srcNum = 6;
  792.             break;
  793.             
  794.             case kJackal:
  795.             if (theEnemies[i].facingRight)
  796.                 theEnemies[i].srcNum = 8;
  797.             else
  798.                 theEnemies[i].srcNum = 10;
  799.             break;
  800.         }
  801.     }
  802.     else                            // If not full height, use "timer" to determine height.
  803.         theEnemies[i].dest.top = theEnemies[i].dest.bottom - theEnemies[i].frame;
  804. }
  805.  
  806. //--------------------------------------------------------------  HandleFallingEnemy
  807.  
  808. // A "falling" enemy is an air borne egg.  The enemy was killed, turned into an egg, …
  809. // and the egg is in freefall.  If the egg comes to rest, it will begin a countdown…
  810. // until it is hatched.
  811.  
  812. void HandleFallingEnemy (short i)
  813. {                                    // Take into account gravity - accelerate egg down.
  814.     theEnemies[i].vVel += kGravity;
  815.                                     // Don't allow velocities to skyrocket.
  816.     if (theEnemies[i].vVel > theEnemies[i].maxVVel)
  817.         theEnemies[i].vVel = theEnemies[i].maxVVel;
  818.     else if (theEnemies[i].vVel < -theEnemies[i].maxVVel)
  819.         theEnemies[i].vVel = -theEnemies[i].maxVVel;
  820.     
  821.     if (evenFrame)                    // Apply friction on even frames (who knows).
  822.     {                                // "Friction" is 1/32nd of the velocity.
  823.         theEnemies[i].hVel -= (theEnemies[i].hVel >> 5);
  824.         if ((theEnemies[i].hVel < 32) && (theEnemies[i].hVel > -32))
  825.         {
  826.             if (theEnemies[i].hVel > 0)
  827.                 theEnemies[i].hVel--;
  828.             else if (theEnemies[i].hVel < 0)
  829.                 theEnemies[i].hVel++;
  830.         }
  831.     }
  832.                                     // Move egg horizontally.
  833.     theEnemies[i].h += theEnemies[i].hVel;
  834.     theEnemies[i].dest.left = theEnemies[i].h >> 4;
  835.     theEnemies[i].dest.right = theEnemies[i].dest.left + 24;
  836.                                     // Move egg vertically.
  837.     theEnemies[i].v += theEnemies[i].vVel;
  838.     theEnemies[i].dest.top = theEnemies[i].v >> 4;
  839.     theEnemies[i].dest.bottom = theEnemies[i].dest.top + 24;
  840.                                     // Check for wrap around.
  841.     if (theEnemies[i].dest.left > 640)
  842.     {
  843.         OffsetRect(&theEnemies[i].dest, -640, 0);
  844.         theEnemies[i].h = theEnemies[i].dest.left << 4;
  845.         OffsetRect(&theEnemies[i].wasDest, -640, 0);
  846.     }
  847.     else if (theEnemies[i].dest.right < 0)
  848.     {
  849.         OffsetRect(&theEnemies[i].dest, 640, 0);
  850.         theEnemies[i].h = theEnemies[i].dest.left << 4;
  851.         OffsetRect(&theEnemies[i].wasDest, 640, 0);
  852.     }
  853.     
  854.     CheckEnemyRoofCollision(i);    // See if egg hit ceiling or lava.
  855.     CheckEnemyPlatformHit(i);    // Handle platform hit (it is here it determines if…
  856.                                 // egg has come to rest and should begin countdown).
  857. }
  858.  
  859. //--------------------------------------------------------------  HandleEggEnemy
  860.  
  861. // This is the "idle" egg mode.  This is a static egg, sitting peacefully on…
  862. // a platform.  Waiting patiently so it might hatch into a death-sphinx and…
  863. // slaughter the player.
  864.  
  865. void HandleEggEnemy (short i)
  866. {
  867.     short        center;
  868.     
  869.     theEnemies[i].frame--;                // Decrement the egg timer!
  870.     if (theEnemies[i].frame < 24)        // When it falls below 24, egg starts shrinking.
  871.     {                                    // Use "frame" to determine height of egg.
  872.         theEnemies[i].dest.top = theEnemies[i].dest.bottom - theEnemies[i].frame;
  873.         if (theEnemies[i].frame <= 0)    // When the egg is completely flat (gone)…
  874.         {                                // then BOOM! a sphinx is spawned!
  875.             theEnemies[i].frame = 0;
  876.             PlayExternalSound(kSpawnSound, kSpawnPriority);
  877.             center = (theEnemies[i].dest.left + theEnemies[i].dest.right) >> 1;
  878.                                         // Resize enemy bounds to new "walking enemy" size.
  879.             theEnemies[i].dest.left = center - 24;
  880.             theEnemies[i].dest.right = center + 24;
  881.             theEnemies[i].wasDest = theEnemies[i].dest;
  882.             theEnemies[i].h = theEnemies[i].dest.left << 4;
  883.             theEnemies[i].v = theEnemies[i].dest.top << 4;
  884.                                         // Set up all other enemy variables.
  885.             theEnemies[i].wasH = theEnemies[i].h;
  886.             theEnemies[i].wasV = theEnemies[i].v;
  887.             theEnemies[i].hVel = 0;
  888.             theEnemies[i].vVel = 0;
  889.             theEnemies[i].mode = kSpawning;
  890.             theEnemies[i].kind++;
  891.             if (theEnemies[i].kind > kJackal)
  892.                 theEnemies[i].kind = kJackal;
  893.             SetEnemyAttributes(i);
  894.         }
  895.     }
  896. }
  897.  
  898. //--------------------------------------------------------------  MoveEnemies
  899.  
  900. // This is the "master" enemy function.  It goes through all the enemies…
  901. // and calls the above functions depending upon an enemy's mode.
  902.  
  903. void MoveEnemies (void)
  904. {
  905.     short        i;
  906.     
  907.     doEnemyFlapSound = FALSE;        // Intially, assume no flap or scrape sounds.
  908.     doEnemyScrapeSound = FALSE;
  909.                                     // Go through each enemy.
  910.     for (i = 0; i < numEnemies; i++)
  911.     {
  912.         switch (theEnemies[i].mode)
  913.         {                            // Handle enemy according to mode it is in.
  914.             case kIdle:                // Enemy not born yet.
  915.             HandleIdleEnemies(i);
  916.             break;
  917.             
  918.             case kFlying:            // Enemy air borne.
  919.             HandleFlyingEnemies(i);
  920.             break;
  921.             
  922.             case kWalking:            // Enemy just born, walking off platform.
  923.             HandleWalkingEnemy(i);
  924.             break;
  925.             
  926.             case kSpawning:            // Enemy growing from a platform.
  927.             HandleSpawningEnemy(i);
  928.             break;
  929.             
  930.             case kFalling:            // Enemy is an egg in flight.
  931.             HandleFallingEnemy(i);
  932.             break;
  933.             
  934.             case kEggTimer:            // Enemy is a patient, idle, silent egg.
  935.             HandleEggEnemy(i);
  936.             break;
  937.             
  938.             case kDeadAndGone:        // Enemy no more - gone for good this level.
  939.             break;
  940.         }
  941.     }
  942.                                     // If any sounds were flagged, play them.
  943.     if (doEnemyFlapSound)
  944.         PlayExternalSound(kFlap2Sound, kFlap2Priority);
  945.     if (doEnemyScrapeSound)
  946.         PlayExternalSound(kScrape2Sound, kScrape2Priority);
  947.                                     // See if enough enemies were killed to advance to…
  948.                                     // next level (wave).
  949.     if ((deadEnemies >= numEnemiesThisLevel) && (countDownTimer == 0))
  950.         countDownTimer = 30;
  951. }
  952.  
  953. //--------------------------------------------------------------  InitHandLocation
  954.  
  955. // This simply sets up the hand.  Puts it deep in the lava (off bottom of screen).
  956.  
  957. void InitHandLocation (void)
  958. {
  959.     SetRect(&theHand.dest, 0, 0, 56, 57);
  960.     OffsetRect(&theHand.dest, 48, 460);
  961. }
  962.  
  963. //--------------------------------------------------------------  HandleHand
  964.  
  965. // This is the hand "AI".  The hand, like the sphinx enemies, has modes.
  966.  
  967. void HandleHand (void)
  968. {
  969.     Rect        whoCares;
  970.     short        hDiff, vDiff, pull, speed;
  971.     
  972.     switch (theHand.mode)
  973.     {
  974.         case kLurking:                // Hand is down, waiting for player to stray near.
  975.         if ((thePlayer.mode == kFlying) && (SectRect(&thePlayer.dest, &grabZone, &whoCares)))
  976.         {                            // If player flies near, hand begins to reach out.
  977.             theHand.mode = kOutGrabeth;
  978.             InitHandLocation();
  979.         }
  980.         break;
  981.         
  982.         case kOutGrabeth:            // Hand is either coming after or has a hold of player.
  983.         case kClutching:
  984.         if (SectRect(&thePlayer.dest, &grabZone, &whoCares))
  985.         {                            // See if player in the "grab/clutch zone".
  986.             hDiff = theHand.dest.left - thePlayer.dest.left;
  987.             vDiff = theHand.dest.top - thePlayer.dest.top;
  988.                                     // Ah!  Player caught.  Move player to correct…
  989.                                     // location relative to the hand (so the player…
  990.                                     // appears to, in fact, be held).
  991.             if (thePlayer.facingRight)
  992.                 hDiff -= 3;
  993.             else
  994.                 hDiff -= 21;
  995.             vDiff -= 29;
  996.                                     // How hard/fast the hand moves depends on level.
  997.             speed = (levelOn >> 3) + 1;
  998.             if (hDiff < 0)
  999.             {
  1000.                 theHand.dest.left += speed;
  1001.                 theHand.dest.right += speed;
  1002.             }
  1003.             else if (hDiff > 0)
  1004.             {
  1005.                 theHand.dest.left -= speed;
  1006.                 theHand.dest.right -= speed;
  1007.             }
  1008.             if (vDiff < 0)
  1009.             {
  1010.                 theHand.dest.top += speed;
  1011.                 theHand.dest.bottom += speed;
  1012.             }
  1013.             else if (vDiff > 0)
  1014.             {
  1015.                 theHand.dest.top -= speed;
  1016.                 theHand.dest.bottom -= speed;
  1017.             }
  1018.                                     // Determine absolute distance player is from hand.
  1019.             if (hDiff < 0)
  1020.                 hDiff = -hDiff;
  1021.             if (vDiff < 0)
  1022.                 vDiff = -vDiff;
  1023.             
  1024.             if ((hDiff < 8) && (vDiff < 8))
  1025.             {                        // If player in the "hot zone", player is nabbed!
  1026.                 theHand.mode = kClutching;
  1027.                 thePlayer.clutched = TRUE;
  1028.                                     // Player's movement is severely dampened.
  1029.                 thePlayer.hVel = thePlayer.hVel >> 3;
  1030.                 thePlayer.vVel = thePlayer.vVel >> 3;
  1031.                                     // Hand pulls player down (strength is greater on…
  1032.                                     // higher levels).
  1033.                 pull = levelOn << 2;
  1034.                 if (pull > 48)        // Set an absolute limit on hand strength.
  1035.                     pull = 48;
  1036.                                     // Pull player donw!
  1037.                 thePlayer.vVel += pull;
  1038.                 theHand.dest.top = thePlayer.dest.top + 29;
  1039.                 theHand.dest.bottom = theHand.dest.top + 57;
  1040.                 if (thePlayer.facingRight)
  1041.                     theHand.dest.left = thePlayer.dest.left + 3;
  1042.                 else
  1043.                     theHand.dest.left = thePlayer.dest.left + 21;
  1044.                 theHand.dest.right = theHand.dest.left + 58;
  1045.             }
  1046.             else                    // If player not in "sweet spot", hand is seeking.
  1047.             {
  1048.                 thePlayer.clutched = FALSE;
  1049.                 theHand.mode = kOutGrabeth;
  1050.             }
  1051.         }
  1052.         else                        // Player not even close to hand…
  1053.         {                            // Hand sinks back down into lava.
  1054.             theHand.dest.top++;
  1055.             theHand.dest.bottom++;
  1056.                                     // When hand is off screen, hand resumes lurking.
  1057.             if (theHand.dest.top > 460)
  1058.                 theHand.mode = kLurking;
  1059.             else
  1060.                 theHand.mode = kOutGrabeth;
  1061.             thePlayer.clutched = FALSE;
  1062.         }
  1063.         break;
  1064.     }
  1065. }
  1066.  
  1067. //--------------------------------------------------------------  InitEye
  1068.  
  1069. // This initializes all the eye's variables.
  1070.  
  1071. void InitEye (void)
  1072. {
  1073.     SetRect(&theEye.dest, 0, 0, 48, 31);
  1074.     OffsetRect(&theEye.dest, 296, 97);
  1075.     theEye.mode = kWaiting;
  1076.     theEye.frame = (numOwls + 2) * 720;
  1077.     theEye.srcNum = 0;
  1078.     theEye.opening = 1;
  1079.     theEye.killed = FALSE;
  1080.     theEye.entering = FALSE;
  1081. }
  1082.  
  1083. //--------------------------------------------------------------  KillOffEye
  1084.  
  1085. // This function handles a "slain" eye!
  1086.  
  1087. void KillOffEye (void)
  1088. {
  1089.     if (theEye.mode == kStalking)
  1090.     {
  1091.         theEye.killed = TRUE;
  1092.         theEye.opening = 1;
  1093.         theEye.entering = FALSE;
  1094.         if (theEye.srcNum == 0)
  1095.             theEye.srcNum = 1;
  1096.     }
  1097.     else
  1098.         InitEye();
  1099. }
  1100.  
  1101. //--------------------------------------------------------------  HandleEye
  1102.  
  1103. // But of course, the eye has modes as well.  This function handles the eye…
  1104. // depending upon the mode it is in.
  1105.  
  1106. void HandleEye (void)
  1107. {
  1108.     short        diffH, diffV, speed;
  1109.     
  1110.     if (theEye.mode == kStalking)        // Eye is alive!
  1111.     {
  1112.         speed = (levelOn >> 4) + 1;        // How fast it moves depends on level.
  1113.         if (speed > 3)
  1114.             speed = 3;
  1115.                                         // When eye appears or dies, it is stationary.
  1116.         if ((theEye.killed) || (theEye.entering))
  1117.         {
  1118.             speed = 0;
  1119.         }
  1120.         else if ((thePlayer.mode != kFlying) && (thePlayer.mode != kWalking))
  1121.         {
  1122.             diffH = theEye.dest.left - 296;
  1123.             diffV = theEye.dest.bottom - 128;
  1124.         }
  1125.         else
  1126.         {
  1127.             diffH = theEye.dest.left - thePlayer.dest.left;
  1128.             diffV = theEye.dest.bottom - thePlayer.dest.bottom;
  1129.         }
  1130.                                         // Find direction to player (no wrap-around for eye).
  1131.         if (diffH > 0)
  1132.         {
  1133.             if (diffH < speed)
  1134.                 theEye.dest.left -= diffH;
  1135.             else
  1136.                 theEye.dest.left -= speed;
  1137.             theEye.dest.right = theEye.dest.left + 48;
  1138.         }
  1139.         else if (diffH < 0)
  1140.         {
  1141.             if (-diffH < speed)
  1142.                 theEye.dest.left -= diffH;
  1143.             else
  1144.                 theEye.dest.left += speed;
  1145.             theEye.dest.right = theEye.dest.left + 48;
  1146.         }
  1147.         if (diffV > 0)
  1148.         {
  1149.             if (diffV < speed)
  1150.                 theEye.dest.bottom -= diffV;
  1151.             else
  1152.                 theEye.dest.bottom -= speed;
  1153.             theEye.dest.top = theEye.dest.bottom - 31;
  1154.         }
  1155.         else if (diffV < 0)
  1156.         {
  1157.             if (-diffV < speed)
  1158.                 theEye.dest.bottom -= diffV;
  1159.             else
  1160.                 theEye.dest.bottom += speed;
  1161.             theEye.dest.top = theEye.dest.bottom - 31;
  1162.         }
  1163.         
  1164.         theEye.frame++;                    // Increment eye frame (timer).
  1165.                                         // Determine correct graphic for eye.
  1166.         if (theEye.srcNum != 0)
  1167.         {
  1168.             if (theEye.frame > 3)        // "Eye-closing frame" holds for 3 frames.
  1169.             {
  1170.                 theEye.frame = 0;
  1171.                 theEye.srcNum += theEye.opening;
  1172.                 if (theEye.srcNum > 3)
  1173.                 {
  1174.                     theEye.srcNum = 3;
  1175.                     theEye.opening = -1;
  1176.                     if (theEye.killed)
  1177.                         InitEye();
  1178.                 }
  1179.                 else if (theEye.srcNum <= 0)
  1180.                 {
  1181.                     theEye.srcNum = 0;
  1182.                     theEye.opening = 1;
  1183.                     theEye.frame = 0;
  1184.                     theEye.entering = FALSE;
  1185.                 }
  1186.             }
  1187.         }
  1188.         else if (theEye.frame > 256)
  1189.         {
  1190.             theEye.srcNum = 1;
  1191.             theEye.opening = 1;
  1192.             theEye.frame = 0;
  1193.         }
  1194.                                         // Get absolute distance from eye to player.
  1195.         diffH = theEye.dest.left - thePlayer.dest.left;
  1196.         diffV = theEye.dest.bottom - thePlayer.dest.bottom;
  1197.         if (diffH < 0)
  1198.             diffH = -diffH;
  1199.         if (diffV < 0)
  1200.             diffV = -diffV;
  1201.                                         // See if player close enough to be killed!
  1202.         if ((diffH < 16) && (diffV < 16) && (!theEye.entering) && 
  1203.                 (!theEye.killed))        // Close enough to call it a kill.
  1204.         {
  1205.             if (theEye.srcNum == 0)        // If eye was open, player is killed.
  1206.             {                            // Strike lightning (hit the player).
  1207.             
  1208.                 StartPixelShatterRect(&thePlayer.dest, 0, 0, kShatterPlayerDeath);
  1209.                 thePlayer.electrical = 15;
  1210.                                                         // Player is smokin' bones!
  1211.                 thePlayer.mode = kFalling;
  1212.                 if (thePlayer.facingRight)
  1213.                     thePlayer.srcNum = 8;
  1214.                 else
  1215.                     thePlayer.srcNum = 9;
  1216.                 thePlayer.dest.bottom = thePlayer.dest.top + 37;
  1217.                 PlayExternalSound(kBoom2Sound, kBoom2Priority);
  1218.             }
  1219.             else                        // If the eye was "blinking", IT was killed!
  1220.             {                            // Player killed the eye!
  1221.                 if (lightningCount == 0)
  1222.                 {                        // Strike the eye with lightning!
  1223.                     lightH = theEye.dest.left + 24;
  1224.                     lightV = theEye.dest.top + 16;
  1225.                                         // Hit 'er with 15 bolts!
  1226.                     lightningCount = 15;
  1227.                 }
  1228.                 theScore += 2000L;        // A big 2000 points for killing the eye!
  1229.                 UpdateScoreNumbers();    // Refresh score display.
  1230.                 PlayExternalSound(kBonusSound, kBonusPriority);
  1231.                 
  1232.                 KillOffEye();            // Slay eye!
  1233.             }                            // Hey, anyone remember that giant eye from…
  1234.         }                                // Johnny Socko and his Flying Robot?
  1235.     }                                    // As a kid, I thought that was cool!
  1236.     else if (theEye.frame > 0)            // Eye has not yet appeared, but waits, lurking!
  1237.     {
  1238.         theEye.frame--;                    // Decrement eye timer.
  1239.         if (theEye.frame <= 0)            // When timer hits zero, eye appears!
  1240.         {
  1241.             theEye.mode = kStalking;    // The eye is after the player!
  1242.             if (lightningCount == 0)    // Strike lightning at eye!
  1243.             {
  1244.                 lightH = theEye.dest.left + 24;
  1245.                 lightV = theEye.dest.top + 16;
  1246.                 lightningCount = 6;
  1247.             }
  1248.             theEye.srcNum = 3;
  1249.             theEye.opening = 1;
  1250.             theEye.entering = TRUE;
  1251.         }
  1252.     }    
  1253. }
  1254.  
  1255. //--------------------------------------------------------------  ResolveEnemyPlayerHit
  1256.  
  1257. // Okay, a bounds test determined that the player and an enemy have collided.
  1258. // This function looks at the two and determines who wins or if it's a draw.
  1259.  
  1260. void ResolveEnemyPlayerHit (short i)
  1261. {
  1262.     short        wasVel, diff, h, v;
  1263.     
  1264.     if ((theEnemies[i].mode == kFalling) || (theEnemies[i].mode == kEggTimer))
  1265.     {                        // Okay, if the enemy is an egg…
  1266.         deadEnemies++;        // simple - the enemy dies.
  1267.         
  1268.         theEnemies[i].mode = kDeadAndGone;
  1269.         theScore += 500L;    // Add that to our score!
  1270.         {
  1271.             Point pt;
  1272.             pt.h = theEnemies[i].dest.left;
  1273.             pt.v = theEnemies[i].dest.top;
  1274.             StartScoreFloater(500, pt);
  1275.         }
  1276.         UpdateScoreNumbers();
  1277.         PlayExternalSound(kBonusSound, kBonusPriority);
  1278.         InitEnemy(i, TRUE);    // Reset the enemy (I guess you could say they're reincarnated.
  1279.     }
  1280.     else                    // Now, here's a real, live sphinx enemy.
  1281.     {                        // Get their difference in altitude.
  1282.         diff = (theEnemies[i].dest.top + 25) - (thePlayer.dest.top + 19);
  1283.         
  1284.         if (diff < -2)        // Player is bested.  :(
  1285.         {                    // Strike player with lightning.
  1286.             thePlayer.electrical = 15;
  1287.             StartPixelShatterRect(&thePlayer.dest, theEnemies[i].hVel, theEnemies[i].vVel, kShatterPlayerDeath);
  1288.  
  1289.             // Player is bones.
  1290.             thePlayer.mode = kFalling;
  1291.             if (thePlayer.facingRight)
  1292.                 thePlayer.srcNum = 8;
  1293.             else
  1294.                 thePlayer.srcNum = 9;
  1295.             thePlayer.dest.bottom = thePlayer.dest.top + 37;
  1296.             PlayExternalSound(kBoom2Sound, kBoom2Priority);
  1297.         }
  1298.         else if (diff > 2)    // Yes!  Enemy is killed!
  1299.         {                    // Well ... we can't kill an enemy who is spawning.
  1300.             if ((theEnemies[i].mode == kSpawning) && (theEnemies[i].frame < 16))
  1301.                 return;
  1302.                             // Resize enemy bounds (use an egg bounds).
  1303.             h = (theEnemies[i].dest.left + theEnemies[i].dest.right) >> 1;
  1304.             if (theEnemies[i].mode == kSpawning)
  1305.                 v = theEnemies[i].dest.bottom - 2;
  1306.             else
  1307.                 v = (theEnemies[i].dest.top + theEnemies[i].dest.bottom) >> 1;
  1308.             theEnemies[i].dest.left = h - 12;
  1309.             theEnemies[i].dest.right = h + 12;
  1310.             if (theEnemies[i].mode == kSpawning)
  1311.                 theEnemies[i].dest.top = v - 24;
  1312.             else
  1313.                 theEnemies[i].dest.top = v - 12;
  1314.             theEnemies[i].dest.bottom = theEnemies[i].dest.top + 24;
  1315.             theEnemies[i].h = theEnemies[i].dest.left << 4;
  1316.             theEnemies[i].v = theEnemies[i].dest.top << 4;
  1317.             
  1318.             StartPixelShatterRect(&theEnemies[i].dest, thePlayer.hVel, thePlayer.vVel, kShatterEnemyDeath);
  1319.             
  1320.                             // Enemy is a falling egg!
  1321.             theEnemies[i].mode = kFalling;
  1322.             theEnemies[i].wasDest = theEnemies[i].dest;
  1323.             theEnemies[i].wasH = theEnemies[i].h;
  1324.             theEnemies[i].wasV = theEnemies[i].v;
  1325.                             // Give player points based on enemy kind.
  1326.             {
  1327.                 unsigned long newScore;
  1328.                 Point pt;
  1329.  
  1330.                 switch (theEnemies[i].kind)
  1331.                 {
  1332.                     case kOwl:
  1333.                     newScore = 500L;
  1334.                     break;
  1335.                     
  1336.                     case kWolf:
  1337.                     newScore = 1000L;
  1338.                     break;
  1339.                     
  1340.                     case kJackal:
  1341.                     newScore = 1500L;
  1342.                     break;
  1343.                 }
  1344.                 
  1345.                 theScore += newScore;
  1346.                             
  1347.                 pt.h = theEnemies[i].dest.left;
  1348.                 pt.v = theEnemies[i].dest.top;
  1349.                 StartScoreFloater(newScore, pt);
  1350.             }
  1351.  
  1352.             UpdateScoreNumbers();
  1353.             PlayExternalSound(kBoom2Sound, kBoom2Priority);
  1354.         }
  1355.         else        // Rare case - neither the player nor the enemy get killed.
  1356.         {            // They'll bounce off one another.
  1357.             if (theEnemies[i].hVel > 0)
  1358.                 theEnemies[i].facingRight = TRUE;
  1359.             else
  1360.                 theEnemies[i].facingRight = FALSE;
  1361.             PlayExternalSound(kScreechSound, kScreechPriority);
  1362.             
  1363.             StartPixelShatter(    (theEnemies[i].dest.left + thePlayer.dest.right) / 2, 
  1364.                                 (theEnemies[i].dest.top + thePlayer.dest.bottom) / 2, 0,0, kShatterPlayerEnemyScrape);
  1365.         }
  1366.         
  1367.         wasVel = thePlayer.hVel;
  1368.         thePlayer.hVel = theEnemies[i].hVel;
  1369.         theEnemies[i].hVel = wasVel;
  1370.         wasVel = thePlayer.vVel;
  1371.         thePlayer.vVel = theEnemies[i].vVel;
  1372.         theEnemies[i].vVel = wasVel;
  1373.     }
  1374. }
  1375.  
  1376. //--------------------------------------------------------------  CheckPlayerEnemyCollision
  1377.  
  1378. // This is a simple "bounds test" for determining player/enemy collisions.
  1379.  
  1380. void CheckPlayerEnemyCollision (void)
  1381. {
  1382.     Rect        whoCares, playTest, wrapTest;
  1383.     short        i;
  1384.     
  1385.     playTest = thePlayer.dest;        // Make a copy of player's bounds.
  1386.     InsetRect(&playTest, 8, 8);        // Shrink it by 8 pixels all 'round.
  1387.     if (thePlayer.wrapping)            // Need to test 2 players if "wraparounding".
  1388.         wrapTest = thePlayer.wrap;
  1389.     InsetRect(&wrapTest, 8, 8);
  1390.                                     // Test all enemies.
  1391.     for (i = 0; i < numEnemies; i++)
  1392.     {                                // Ignore non-existant enemies.
  1393.         if ((theEnemies[i].mode != kIdle) && (theEnemies[i].mode != kDeadAndGone))
  1394.         {                            // Simple bounds test.
  1395.             if (SectRect(&playTest, &theEnemies[i].dest, &whoCares))
  1396.             {                        // Call function to determine who wins (or tie).
  1397.                 ResolveEnemyPlayerHit(i);
  1398.             }                        // If "wrap-arounding", test other rect.
  1399.             else if (thePlayer.wrapping)
  1400.             {
  1401.                 if (SectRect(&wrapTest, &theEnemies[i].dest, &whoCares))
  1402.                     ResolveEnemyPlayerHit(i);
  1403.             }
  1404.         }
  1405.     }
  1406. }
  1407.  
  1408.